//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.ComponentModel;
using System.Data;
using System.Text;
using System.Workflow.ComponentModel;
using WorkflowData;

namespace Microsoft.Research.ScientificWorkflow.Activities
{
    [Name("Hyper Cube To Data Table Transformation", "Cube to Table")]
    [Description("Flattens the data of a HyperCube and stores it into a two-dimensional table. Use the Collapse input parameters to indicate which dimensions should be included in the first column of the table. At least one dimension should not be collapsed.")]
    [WorkflowCategory("Data")]
    public class HyperCubeToDataTable : Activity
	{

        #region Dependency Properties

		public static DependencyProperty InputHyperCubeProperty = DependencyProperty.Register("InputHyperCube",
            typeof(HyperCube), typeof(HyperCubeToDataTable), new PropertyMetadata(null));

        [RequiredInputParam]
        [Name("Input HyperCube", "HyperCube")]
        [Description("The HyperCube data that contains the input to be processed into a table.")]
		public HyperCube InputHyperCube
		{
			get
			{
                return ((HyperCube)(base.GetValue(HyperCubeToDataTable.InputHyperCubeProperty)));
			}
			set
			{
                base.SetValue(HyperCubeToDataTable.InputHyperCubeProperty, value);
			}
		}

        public static DependencyProperty OperationProperty = DependencyProperty.Register("Operation",
            typeof(string), typeof(HyperCubeToDataTable), new PropertyMetadata("Average"));

        [OptionalInputParam]
        [Name("Operation")]
        [Description("The math operation to perform when collapsing the data. Currently supported operations are 'Average' and 'Sum'.")]
        public string Operation
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeToDataTable.OperationProperty)));
            }
            set
            {
                base.SetValue(HyperCubeToDataTable.OperationProperty, value);
            }
        }

        public static DependencyProperty InvertDataProperty = DependencyProperty.Register("InvertData",
            typeof(bool), typeof(HyperCubeToDataTable), new PropertyMetadata(false));

        [OptionalInputParam]
        [Name("Invert Data", "Invert")]
        [Description("Indicates whether or not to multiply all data by -1 once the operation is complete.")]
        public bool InvertData
        {
            get
            {
                return ((bool)(base.GetValue(HyperCubeToDataTable.InvertDataProperty)));
            }
            set
            {
                base.SetValue(HyperCubeToDataTable.InvertDataProperty, value);
            }
        }

        public static DependencyProperty CollapseXProperty = DependencyProperty.Register("CollapseX",
            typeof(bool), typeof(HyperCubeToDataTable), new PropertyMetadata(true));

        [OptionalInputParam]
        [Name("Collapse X")]
        [Description("Indicates whether or not the processing of the data should apply its operation over the X dimension of the HyperCube.")]
        public bool CollapseX
        {
            get
            {
                return ((bool)(base.GetValue(HyperCubeToDataTable.CollapseXProperty)));
            }
            set
            {
                base.SetValue(HyperCubeToDataTable.CollapseXProperty, value);
            }
        }

        public static DependencyProperty CollapseYProperty = DependencyProperty.Register("CollapseY",
            typeof(bool), typeof(HyperCubeToDataTable), new PropertyMetadata(true));

        [OptionalInputParam]
        [Name("Collapse Y")]
        [Description("Indicates whether or not the processing of the data should apply its operation over the Y dimension of the HyperCube.")]
        public bool CollapseY
        {
            get
            {
                return ((bool)(base.GetValue(HyperCubeToDataTable.CollapseYProperty)));
            }
            set
            {
                base.SetValue(HyperCubeToDataTable.CollapseYProperty, value);
            }
        }

        public static DependencyProperty CollapseZProperty = DependencyProperty.Register("CollapseZ",
            typeof(bool), typeof(HyperCubeToDataTable), new PropertyMetadata(true));

        [OptionalInputParam]
        [Name("Collapse Z")]
        [Description("Indicates whether or not the processing of the data should apply its operation over the Z dimension of the HyperCube.")]
        public bool CollapseZ
        {
            get
            {
                return ((bool)(base.GetValue(HyperCubeToDataTable.CollapseZProperty)));
            }
            set
            {
                base.SetValue(HyperCubeToDataTable.CollapseZProperty, value);
            }
        }

        public static DependencyProperty CollapseTProperty = DependencyProperty.Register("CollapseT",
            typeof(bool), typeof(HyperCubeToDataTable), new PropertyMetadata(true));

        [OptionalInputParam]
        [Name("Collapse T")]
        [Description("Indicates whether or not the processing of the data should apply its operation over the T dimension of the HyperCube.")]
        public bool CollapseT
        {
            get
            {
                return ((bool)(base.GetValue(HyperCubeToDataTable.CollapseTProperty)));
            }
            set
            {
                base.SetValue(HyperCubeToDataTable.CollapseTProperty, value);
            }
        }


        public static DependencyProperty OutputTableProperty = DependencyProperty.Register("OutputTable",
            typeof(DataTable), typeof(HyperCubeToDataTable), new PropertyMetadata(null));

        [OutputParam]
        [Name("Output DataTable", "DataTable")]
        [Description("A collapsed representation of the data coming from the input HyperCube")]
        public DataTable OutputTable
        {
            get
            {
                return ((DataTable)(base.GetValue(HyperCubeToDataTable.OutputTableProperty)));
            }
            set
            {
                base.SetValue(HyperCubeToDataTable.OutputTableProperty, value);
            }
        }

        

        #endregion

		protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
		{
            DataTable result = new DataTable();
            result.TableName = "resultTable";
            HyperCube cube = InputHyperCube;
            
            // Exit early if there is no input data
            if (cube == null)
                return ActivityExecutionStatus.Closed;

            InitColumns(result, cube);
            ProcessData(result, cube);

            OutputTable = result;

            return ActivityExecutionStatus.Closed;
		}

        private void ProcessData(DataTable table, HyperCube cube)
        {
            if (String.IsNullOrEmpty(Operation))
                Operation = "Average";

            // Axis Lengths
            int xLen = InputHyperCube.GetAxisLength(HyperCube.Axis.X);
            int yLen = InputHyperCube.GetAxisLength(HyperCube.Axis.Y);
            int zLen = InputHyperCube.GetAxisLength(HyperCube.Axis.Z);
            int tLen = InputHyperCube.GetAxisLength(HyperCube.Axis.T);

            if (xLen < 1) xLen = 1;
            if (yLen < 1) yLen = 1;
            if (zLen < 1) zLen = 1;
            if (tLen < 1) tLen = 1;

            if (!CollapseX)
            {
                for (int x = 0; x < xLen; x++)
                {
                    DataRow data = table.NewRow();
                    data[0] = cube.XDimensionValues[x];

                    for (int c = 0; c < cube.Schema.ColumnCount; c++)
                    {
                        double sum = 0.0;
                        int count = 0;

                        for (int y = 0; y < yLen; y++)
                        {
                            for (int z = 0; z < zLen; z++)
                            {
                                for (int t = 0; t < tLen; t++)
                                {
                                    object val = InputHyperCube[x, y, z, t][c];
                                    double numVal = Convert.ToDouble(val);

                                    if (InvertData)
                                        numVal *= -1.0;

                                    sum += numVal;
                                    count++;
                                } // end for t
                            } // end for z
                        } // end for y

                        if (Operation.Equals("sum", StringComparison.InvariantCultureIgnoreCase))
                        {
                            data[c + 1] = sum;
                        }
                        else
                        {
                            data[c + 1] = sum / (double)count;
                        }
                    } // end for c

                    table.Rows.Add(data);

                } // end for x
            }
        }

        private void InitColumns(DataTable table, HyperCube cube)
        {
            StringBuilder firstColBuff = new StringBuilder();
            bool dimAdded = false;
            
            if (!CollapseX)
            {
                firstColBuff.Append(cube.GetDimensionName(HyperCube.Axis.X));
                dimAdded = true;
            }

            if (!CollapseY)
            {
                if (dimAdded)
                    firstColBuff.Append(", ");
                firstColBuff.Append(cube.GetDimensionName(HyperCube.Axis.Y));
                dimAdded = true;
            }

            if (!CollapseZ)
            {
                if (dimAdded)
                    firstColBuff.Append(", ");
                firstColBuff.Append(cube.GetDimensionName(HyperCube.Axis.Z));
                dimAdded = true;
            }

            if (!CollapseT)
            {
                if (dimAdded)
                    firstColBuff.Append(", ");
                firstColBuff.Append(cube.GetDimensionName(HyperCube.Axis.T));
                dimAdded = true;
            }

            if (!dimAdded)
                firstColBuff.Append("All Points");

            table.Columns.Clear();
            table.Columns.Add(new DataColumn(firstColBuff.ToString(), typeof(string)));

            // Now add the data columns
            for (int i = 0; i < cube.Schema.ColumnCount; i++)
            {
                table.Columns.Add(new DataColumn(cube.Schema.GetColumnName(i), typeof(double)));
            }
        }
	}
}
